package dk.sdu.amaze;

import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.Point;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;

import javax.swing.JFrame;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import TUIO.TuioClient;

public class Amaze {
	private static final String levelsFile = "levels.xml";
	private static final String bestTimesFile = "results.dat";

	private boolean fullscreen = false;

	private final int window_width = 800;
	private final int window_height = 600;

	private GraphicsDevice device;
	private JFrame frame;

	private TuioClient client = null;

	private State state;
	private ArrayList<Level> levels;
	private HashMap<Integer, Integer> bestTimes;

	/*
	 * Constructor
	 */
	public Amaze(TuioClient client) {
		this.levels = readLevels(levelsFile);
		this.bestTimes = readBestTimes(bestTimesFile);
		// this.state = new StateA(this);
		// this.state = new StateB(this);
		this.state = new StateA(this);
		this.device = GraphicsEnvironment.getLocalGraphicsEnvironment()
				.getDefaultScreenDevice();
		this.client = client;

		setupWindow();
		showWindow();

		client.addTuioListener(state);
		client.connect();
	}

	public void setupWindow() {
		frame = new JFrame();

		frame.add(state);

		frame.setTitle("The Amaze");
		frame.setResizable(false);

		// frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent evt) {
				System.exit(0);
			}
		});

		frame.addKeyListener(new KeyAdapter() {
			public void keyPressed(KeyEvent evt) {
				if (evt.getKeyCode() == KeyEvent.VK_ESCAPE) {
					System.exit(0); // exit game?
				} else if (evt.getKeyCode() == KeyEvent.VK_F1) {
					destroyWindow();
					setupWindow();
					fullscreen = !fullscreen;
					showWindow();
				}
			}
		});
	}

	public void showWindow() {

		if (fullscreen) {
			int width = (int) Toolkit.getDefaultToolkit().getScreenSize()
					.getWidth();
			int height = (int) Toolkit.getDefaultToolkit().getScreenSize()
					.getHeight();

			frame.setSize(width, height);
			frame.setUndecorated(true);

			device.setFullScreenWindow(frame);
		} else {
			int width = window_width;
			int height = window_height;

			frame.pack();
			Insets insets = frame.getInsets();
			frame.setSize(width, height + insets.top);
		}

		frame.setVisible(true);
		frame.repaint();
	}

	public void destroyWindow() {

		frame.setVisible(false);
		if (fullscreen) {
			device.setFullScreenWindow(null);
		}
		frame = null;
	}

	public void setState(State nextState) {
		if (state != null) {
			client.removeTuioListener(state);
			frame.remove(state);
		}

		state = nextState;

		frame.add(state); // repaint() ?
		frame.validate();
		client.addTuioListener(state);
	}

	private HashMap<Integer, Integer> readBestTimes(String filename) {
		HashMap<Integer, Integer> times = null;


		File f = new File(filename);

		if (f.exists()) {
			try {
				FileInputStream fis = new FileInputStream(f);
				ObjectInputStream inStream = new ObjectInputStream(fis);
				times = (HashMap<Integer, Integer>) inStream.readObject();
				inStream.close();
				return times;
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		}

		return new HashMap<Integer, Integer>(); // if file could not be

	}

	private void saveBestTimes(String filename, HashMap<Integer, Integer> times) {
		try {
			FileOutputStream fos = new FileOutputStream(filename);
			ObjectOutputStream outStream = new ObjectOutputStream(fos);
			outStream.writeObject(times);
			outStream.flush();
			outStream.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void save() {
		saveBestTimes(bestTimesFile, bestTimes);
	}

	public ArrayList<Level> readLevels(String filename) {
		ArrayList<Level> levels = new ArrayList<Level>();

		try {
			File file = new File(filename);
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();
			Document doc = db.parse(file);
			doc.getDocumentElement().normalize();

			NodeList levelNodes = doc.getElementsByTagName("level");
			for (int s = 0; s < levelNodes.getLength(); s++) {
				Element levelElement = (Element) levelNodes.item(s);

				Integer id = new Integer(levelElement.getAttribute("id"));
				Integer rows = new Integer(levelElement.getAttribute("rows"));
				Integer cols = new Integer(levelElement.getAttribute("cols"));
				Integer maxtime = new Integer(levelElement
						.getAttribute("maxtime"));

				LinkedList<Point> path = new LinkedList<Point>();

				Element pathElement = (Element) levelElement
						.getElementsByTagName("path").item(0);

				NodeList stepNodes = pathElement.getElementsByTagName("step");
				for (int j = 0; j < stepNodes.getLength(); j++) {
					Element stepElement = (Element) stepNodes.item(j);

					Integer x = new Integer(stepElement.getAttribute("x"));
					Integer y = new Integer(stepElement.getAttribute("y"));

					path.add(new Point(x, y));
				}

				levels.add(new Level(id, rows, cols, maxtime, path));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		return levels;
	}



	public int getBestTime(int levelId) {
		Integer bestTime = bestTimes.get(new Integer(levelId));

		if (bestTime == null)
			return 0;

		return bestTime;
	}


	public void setBestTime(int id, int newTime) {
		if (newTime <= 0)
			return;

		bestTimes.put(id, newTime);
		save();
	}

	public ArrayList<Level> getLevels() {
		return levels;
	}

	public static void main(String argv[]) {
		TuioClient client = null;

		switch (argv.length) {
		case 1:
			try {
				client = new TuioClient(Integer.parseInt(argv[0]));
			} catch (Exception e) {
				System.out.println("usage: java Amaze [port]");
				System.exit(0);
			}
			break;
		case 0:
			client = new TuioClient();
			break;
		default:
			System.out.println("usage: java Amaze [port]");
			System.exit(0);
			break;
		}

		if (client != null) {
			new Amaze(client); // ?
		} else {
			System.out.println("usage: java Amaze [port]");
			System.exit(0);
		}
	}

	public int getLevelNumber(Level level) {
		for(int i=0; i<levels.size(); i++) {
			if (levels.get(i) == level)
				return i;
		}

		return -1;
	}


}
